<?php
class Slash_Client_Client
{
	public static $icalDate = "Ymd\THis\Z";
	public static $icalDateFloating = "Ymd\THis";
	
	private static $escapes = array(
		'\\\\'=>'\\',
		'\\;'=>";",
		'\\,'=>",",
		'\N'=>"\n",
		'\\n'=>"\n",
	);
	
	private $server;
	private $user;
	private $password; 
	
	public function __construct($server, $user, $password)
	{
		$this->server = $server;
		$this->user = $user;
		$this->password = $password;
	}
	
	public static function unescapeText($text)
	{
		return str_replace(array_keys(self::$escapes), array_values(self::$escapes), $text);
	}
	

		
	/*
	 * Return an array of events, each consisting of an array with keys
	 * The array looks like
	 * array(
	 * 	 href=> array(
	 * 		 "props" => array(DOMNode),
	 * 		 "caldata" => calendar-data-object
	 *   )
	 * )
	 */
	public function findEvents($uri, DateTime $from, DateTime $to, $properties)
	{
		$rangeStart = $from->format(self::$icalDate);
		$rangeEnd = $to->format(self::$icalDate);
		$results = array();
		$nsCal = "urn:ietf:params:xml:ns:caldav";
		$nsDav = "DAV:";
		$data = "<C:calendar-query xmlns:D='$nsDav' xmlns:C='$nsCal'>
     <D:prop>
     </D:prop>
     <C:filter>
       <C:comp-filter name='VCALENDAR'>
         <C:comp-filter name='VEVENT'>
           <C:time-range start='$rangeStart'
                           end='$rangeEnd'/>
         </C:comp-filter>
       </C:comp-filter>
     </C:filter>
   </C:calendar-query>";
		$doc = new DOMDocument();
		$doc->loadXML($data);
		$propElement = $doc->getElementsByTagNameNS("DAV:", "prop")->item(0);
		foreach($properties as $property)
		{
			if (preg_match("/{([^}]+)}(.*)/", $property, $matches))
			{
				//print "Adding prop element to query: $matches[1], $matches[2]";
				$prop = $doc->createElementNS($matches[1], $matches[2]);
				$propElement->appendChild($prop);
			}
		}
		$data = $doc->saveXML();				
		
		//print "Sending: $data\n";
	 
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "REPORT");
		curl_setopt($curl, CURLOPT_URL, $this->server . $uri);
		curl_setopt($curl, CURLOPT_USERPWD, "$this->user:$this->password");
		curl_setopt($curl, CURLOPT_HTTPHEADER, 
			array('Content-Length: ' . strlen($data), 
					"Depth: 1")
		);
		curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		$xml = curl_exec($curl);
		
		print "<!-- Received: $xml -->\n";
		$dom = new DOMDocument();
		$dom->loadXML($xml);
		$xpath = new DOMXPath($dom);
		$dav = $dom->lookupPrefix($nsDav);
		$cal = $dom->lookupPrefix($nsCal);
		//echo "DAV: prefix=$dav, CalDAV: prefix=$cal\n";
		
		$query = "/$dav:multistatus/$dav:response/$dav:propstat[contains(/$dav:status, '200')]/$dav:prop/$cal:calendar-data";
		$query = "/$dav:multistatus/$dav:response/$dav:propstat[contains($dav:status, '200')]/$dav:prop/$cal:calendar-data";
		print "<!-- XPath query=$query -->\n";
		$nlcd = $xpath->query($query);
		$query = "/$dav:multistatus/$dav:response/$dav:href";
		//print "XPath query=$query\n";
		$nlhref = $xpath->query($query);
		$query = "/$dav:multistatus/$dav:response/$dav:propstat[contains($dav:status, '200')]/$dav:prop";
		//print "XPath query=$query\n";
		$nlprop = $xpath->query($query);
		$lencd = $nlcd->length;
		$lenprop = $nlprop->length;
		$lenhref = $nlhref->length;
		//print "Found $lencd calendar-data nodes, $lenhref href nodes, $lenprop prop nodes\n";
		for ($i = 0; $i < $lencd; $i++)
		{			
			$result = array();
			$cdnode = $nlcd->item($i);
			$ical = $cdnode->textContent;
			//Unfold folded lines
			$ical = str_replace("\r\n ", "", $ical);
			//Break the caldata into lines
			$arEvent = explode("\n", $ical);
			//Parse the lines into a VCalendar object
			$calobj = Slash_Client_VCalendar::parseObject($arEvent);
			
			$hrefnode = $nlhref->item($i);
			$href = $hrefnode->textContent;
			$propnode = $nlprop->item($i);
			$nlprops = $propnode->getElementsByTagName("*");
			$numprops = $nlprops->length;
			$arprops = array();
			for ($j = 0; $j < $numprops; $j++)
			{
				$prop = $nlprops->item($j);
				if ($prop->nodeName != "$cal:calendar-data")
				{
					$nsuri = $prop->namespaceURI;
					$localName = $prop->localName;
					$arprops["{" . $nsuri . "}$localName"] = $prop;
				}
			}
			$entry = array("props"=>$arprops, "caldata"=>$calobj);
			$results[$href] = $entry;
		}
		
		return $results;
	}
	
	public function getEvent($href, $properties)
	{
		$results = array();
		$nsDav = "DAV:";
		$nsCal = "urn:ietf:params:xml:ns:caldav";
		$data = "<D:propfind xmlns:D='$nsDav' xmlns:C='$nsCal'>
     <D:prop>
     </D:prop>
   </D:propfind>";
		$doc = new DOMDocument();
		$doc->loadXML($data);
		$propElement = $doc->getElementsByTagNameNS("DAV:", "prop")->item(0);
		foreach($properties as $property)
		{
			if (preg_match("/{([^}]+)}(.*)/", $property, $matches))
			{
				//print "Adding prop element to query: $matches[1], $matches[2]";
				$prop = $doc->createElementNS($matches[1], $matches[2]);
				$propElement->appendChild($prop);
			}
		}
		$data = $doc->saveXML();				
		
		//print "Sending (for $href): $data\n";
	 
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PROPFIND");
		curl_setopt($curl, CURLOPT_URL, $this->server . $href);
		curl_setopt($curl, CURLOPT_USERPWD, "$this->user:$this->password");
		curl_setopt($curl, CURLOPT_HTTPHEADER, array("Depth: 0"));
		curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		$xml = curl_exec($curl);
		
		print "<!-- Received: $xml -->\n";
		$dom = new DOMDocument();
		$dom->loadXML($xml);
		$xpath = new DOMXPath($dom);
		$dav = $dom->lookupPrefix($nsDav);
		$cal = $dom->lookupPrefix($nsCal);
		//echo "DAV: prefix=$dav, CalDAV: prefix=$cal\n";
		
		$query = "/$dav:multistatus/$dav:response/$dav:propstat[contains(/$dav:status, '200')]/$dav:prop/$cal:calendar-data";
		$query = "/$dav:multistatus/$dav:response/$dav:propstat[contains($dav:status, '200')]/$dav:prop/$cal:calendar-data";
		//print "XPath query=$query\n";
		$nlcd = $xpath->query($query);
		$query = "/$dav:multistatus/$dav:response/$dav:href";
		//print "XPath query=$query\n";
		$nlhref = $xpath->query($query);
		$query = "/$dav:multistatus/$dav:response/$dav:propstat[contains($dav:status, '200')]/$dav:prop";
		//print "XPath query=$query\n";
		$nlprop = $xpath->query($query);
		$lencd = $nlcd->length;
		$lenprop = $nlprop->length;
		$lenhref = $nlhref->length;
		//print "Found $lencd calendar-data nodes, $lenhref href nodes, $lenprop prop nodes\n";
		for ($i = 0; $i < $lencd; $i++)
		{			
			$result = array();
			$cdnode = $nlcd->item($i);
			$ical = $cdnode->textContent;
			//Unfold folded lines
			$ical = str_replace("\r\n ", "", $ical);
			//Break the caldata into lines
			$arEvent = explode("\n", $ical);
			//Parse the lines into a VCalendar object
			$calobj = Slash_Client_VCalendar::parseObject($arEvent);
			
			$hrefnode = $nlhref->item($i);
			$href = $hrefnode->textContent;
			$propnode = $nlprop->item($i);
			$nlprops = $propnode->getElementsByTagName("*");
			$numprops = $nlprops->length;
			$arprops = array();
			for ($j = 0; $j < $numprops; $j++)
			{
				$prop = $nlprops->item($j);
				if ($prop->nodeName != "$cal:calendar-data")
				{
					$nsuri = $prop->namespaceURI;
					$localName = $prop->localName;
					$arprops["{" . $nsuri . "}$localName"] = $prop;
				}
			}
			$entry = array("props"=>$arprops, "caldata"=>$calobj);
			$results[$href] = $entry;
		}
		
		return $results;
	}

	public function parseObject(&$vcal, $objName)
	{
		print "Parsing $objName object\n";
		while ($line = next($vcal))
		{
			if (preg_match("/BEGIN:(.*)\r$/", $line, $matches))
			{
				$this->parseObject($vcal, $matches[1]);
			}
			if (preg_match("/END:(.*)/", $line, $matches))
			{
				if ($matches[1] == $objName)
				{
					return;
				}
			}
		}
	}
	
}
	
?>	
